﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SwarmMappingAlpha
{
     public class World
    {
        private Dictionary<String, Node> nodesStored;
        private List<Edge> _edgeCollection;
        private List<Edge> minimumSpanningTree;
        private mappedArray weightGraph;
        public int numberOfNodes {get;set;}
        private int heightWorld;
        private int widthWorld;
        public static int sizeOfNode;
        private string[] randomOrder;
        private int currentIndexInRandomOrderArray;

        //Delegate and event update listeners when a new MST is created
        public delegate void mstGenerated(List<Edge> minimumSpanningTree);
        public event mstGenerated newMstCreated;



        public List<Edge> edgeCollection
        {
            get { return _edgeCollection; }
        }
        public Dictionary<String, Node> getNodeCollection()
        {
            return this.nodesStored;
        }

        public World(int worldHeight, int worldWidth, int nodeSize)
        {
            heightWorld = worldHeight;
            widthWorld = worldWidth;
            this.nodesStored = CollectionFactory.buildNodeDictionary(worldWidth, worldHeight, nodeSize);
            this.numberOfNodes = nodesStored.Count;
            this.weightGraph = new mappedArray(this.numberOfNodes);
            this._edgeCollection = new List<Edge>();
            minimumSpanningTree = new List<Edge>();
            sizeOfNode = nodeSize;
            randomOrder = generateRandomOrderForDroneTraversing();
            currentIndexInRandomOrderArray = 0;
        }

        public Node getNextEdgeToVisit()
        {
            String result = "";
            if (randomOrder != null && currentIndexInRandomOrderArray < randomOrder.Length - 1)
            {
                result = randomOrder[currentIndexInRandomOrderArray];
                currentIndexInRandomOrderArray++;
            }
            Node nodeToTravelTo = null;
            nodesStored.TryGetValue(result, out nodeToTravelTo);
            return nodeToTravelTo;
        }

        private string[] generateRandomOrderForDroneTraversing()
        {
            if (nodesStored.Count != 0)
            {
                int i = 0;
                string[] randomOrderArray = new string[nodesStored.Count];
                foreach (KeyValuePair<string, Node> nde in nodesStored)
                {
                    randomOrderArray[i] = nde.Key;
                    i++;
                }

                Random randomGen = new Random();

                for (int j = 0; j < randomOrderArray.Length; j++)
                {
                    string tempHolder = randomOrderArray[j];

                    int randomSpot = randomGen.Next(randomOrderArray.Length);
                    randomOrderArray[j] = randomOrderArray[randomSpot];
                    randomOrderArray[randomSpot] = tempHolder;

                }
                return randomOrderArray;
            }

            return null;  
        }

        /// <summary>
        /// adds a new edge to the given map representing the world
        /// </summary>
        /// <param name="startNodeCoord">coordinate of the starting node</param>
        /// <param name="endNodeCoord">coordinate of the end node</param>
        /// <param name="weight">weight of the new edge</param>
        public void addNewEdge(string startNodeCoord, string endNodeCoord, int weight)
        {
            this._edgeCollection.Add(new Edge(weight, this.nodesStored[startNodeCoord],this.nodesStored[endNodeCoord]));
            this.nodesStored[startNodeCoord].visited = true;
            this.nodesStored[endNodeCoord].visited = true;
        }

        /// <summary>
        /// adds a new edge to the given map representing the world
        /// </summary>
        /// <param name="startNodeCoord">coordinate of the starting node</param>
        /// <param name="endNodeCoord">coordinate of the end node</param>
        /// <param name="weight">weight of the new edge</param>
        public void addNewEdge(int x1, int y1, int x2, int y2, int weight)
        {
            string startNodeCoord = "";
            string endNodeCoord = "";
            startNodeCoord = x1.ToString().PadLeft(3,'0') + (y1.ToString().PadLeft(3,'0'));
            endNodeCoord = x2.ToString().PadLeft(3, '0') + (y2.ToString().PadLeft(3, '0'));
            this._edgeCollection.Add(new Edge(weight, this.nodesStored[startNodeCoord], this.nodesStored[endNodeCoord]));
        }

        //Break up edges to sets of connected edges. Not implemented
        public List<List<Edge>> breakEdgeCollection(List<Edge> edges)
        {
            return null;
        }

        public void MST()
        {
            minimumSpanningTree = this.MST(this._edgeCollection, this.nodesStored.Values.ToList());
        }

        public List<Edge> getMinimumSpanningTree()
        {
            if (minimumSpanningTree != null)
            {
                return minimumSpanningTree;
            }

            return null;
        }

        /// <summary>
        /// Calculates the most efficient tree that connects all nodes
        /// </summary>
        /// <param name="edges">cumulative list of edges</param>
        /// <param name="nodes">list of all nodes</param>
        /// <returns></returns>
        private List<Edge> MST(List<Edge> edges, List<Node> nodes)
        {
            Random r = new Random();
            List<Node> ConnectedNodes = new List<Node>();
            List<Edge> tempEdges = new List<Edge>();
            Node N = nodes[r.Next(nodes.Count)];
            while (!N.visited)
            {
                N = nodes[r.Next(nodes.Count)];
            }
            ConnectedNodes.Add(N);
            Edge tempEdge;
            for (int i = 0; i < nodes.Count - 1; i++)
            {
                tempEdge = new Edge(9000, null, null);
                foreach (Node cNode in ConnectedNodes)
                {
                    foreach (Edge E in edges)
                    {
                        if ((E.Contains(cNode) && E.weight < tempEdge.weight) && !(ConnectedNodes.Contains(E.startNode) && ConnectedNodes.Contains(E.endNode)))
                        {
                            tempEdge = E;
                        }
                    }

                }
                tempEdges.Add(tempEdge);
                if (ConnectedNodes.Contains(tempEdge.startNode))
                {
                    ConnectedNodes.Add(tempEdge.endNode);
                }
                ConnectedNodes.Add(tempEdge.startNode);
            }
            return tempEdges;
        }

        public int calcualteDistance(int x1, int y1, int x2, int y2)
        {
            //Calculate the distance between the target and the actual location
            double xDifference = x2 - x1;
            double yDifference = y2 - y1;

            double xDifferenceSquared = Math.Pow(xDifference, 2);
            double yDifferenceSquared = Math.Pow(yDifference, 2);

            int DronesDistanceFromTarget = (int)Math.Sqrt(xDifferenceSquared + yDifferenceSquared);

            return DronesDistanceFromTarget / 6;
        }

        public int calculateHeading(int x1, int y1, int x2, int y2)
        {
            Double theta = 0;
            Double opposite = Math.Abs(y2-y1);
            Double adjacent = Math.Abs(x2-x1);
            if (x1 > x2)
            {
                theta = 270;
                if (y1 > y2)
                {
                    theta -= (Math.Atan(opposite / adjacent)) * (180/Math.PI);
                }
                else
                {
                    theta += (Math.Atan(opposite / adjacent) * (180 / Math.PI));
                }
            }
            else
            {
                theta = 90;
                if (y1 > y2)
                {
                    theta += Math.Atan(opposite / adjacent) * (180 / Math.PI);
                }
                else
                {
                    theta -= Math.Atan(opposite / adjacent) * (180 / Math.PI);
                }
            }
            return (int)Math.Round(theta);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////
        //                                                                                                    //
        //                                        Tests                                                       //  
        //                                                                                                    //  
        ////////////////////////////////////////////////////////////////////////////////////////////////////////
       
         public void test()
        {
            #region test1
            /*List<Node> nlist = new List<Node>();
            Node a = new Node(5, 5, 0);
            Node b = new Node(10, 10, 1);
            Node c = new Node(15, 5, 2);
            Node d = new Node(0, 0, 3);
            Node e = new Node(0, 5, 4);
            Node f = new Node(10, 0, 5);
            Node g = new Node(15, 0, 6);
            Node h = new Node(15, 0, 7);
            Node i = new Node(15, 0, 8);
            Node j = new Node(15, 0, 9);
            Node k = new Node(15, 0, 10);
            Node l = new Node(15, 0, 11);
            nlist.Add(a);
            nlist.Add(b);
            nlist.Add(c);
            nlist.Add(d);
            nlist.Add(e);
            nlist.Add(f);
            nlist.Add(g);
            nlist.Add(h);
            nlist.Add(i);
            nlist.Add(j);
            nlist.Add(k);
            nlist.Add(l);

            List<Edge> elist = new List<Edge>();
            elist.Add(new Edge(3, a, b));
            elist.Add(new Edge(4, a, e));
            elist.Add(new Edge(1, f, a));
            elist.Add(new Edge(6, j, a));
            elist.Add(new Edge(6, b, c));
            elist.Add(new Edge(2, b, h));
            elist.Add(new Edge(7, g, b));
            elist.Add(new Edge(4, f, b));
            elist.Add(new Edge(7, d, c));
            elist.Add(new Edge(4, d, h));
            elist.Add(new Edge(3, h, l));
            elist.Add(new Edge(5, g, l));
            elist.Add(new Edge(7, k, l));
            elist.Add(new Edge(3, g, j));
            elist.Add(new Edge(7, f, j));
            elist.Add(new Edge(6, j, k));
            elist.Add(new Edge(4, j, i));
            elist.Add(new Edge(6, j, e));
            elist.Add(new Edge(7, e, i));*/
            #endregion

            #region test 2
            List<Node> nlist = new List<Node>();
            Node a = new Node(5, 5, 0);
            Node b = new Node(10, 10, 1);
            Node c = new Node(15, 5, 2);
            Node d = new Node(0, 0, 3);
            Node e = new Node(0, 5, 4);
            Node f = new Node(10, 0, 5);
            Node g = new Node(15, 0, 6);
            nlist.Add(a);
            nlist.Add(b);
            nlist.Add(c);
            nlist.Add(d);
            nlist.Add(e);
            nlist.Add(f);
            nlist.Add(g);

            List<Edge> elist = new List<Edge>();
            elist.Add(new Edge(5, a, d));
            elist.Add(new Edge(7, a, b));
            elist.Add(new Edge(9, d, b));
            elist.Add(new Edge(8, b, c));
            elist.Add(new Edge(7, b, e));
            elist.Add(new Edge(5, c, e));
            elist.Add(new Edge(15, d, e));
            elist.Add(new Edge(8, e, f));
            elist.Add(new Edge(6, d, f));
            elist.Add(new Edge(9, e, g));
            elist.Add(new Edge(11, f, g));
            #endregion

            elist = this.MST(elist, nlist);
            int totalWeight = 0;
            foreach (Edge edg in elist)
            {
                Console.WriteLine(edg.startNode.nodeNumber.ToString() + ", " + edg.endNode.nodeNumber.ToString() + ", " + edg.weight.ToString());
                totalWeight += edg.weight;
            }
            Console.WriteLine(totalWeight);
            Console.Read();
        }
    }
}
